<?php

/**
 * EasyGD - A simple class to handle images in PHP with GD
 *
 * Basic Usage:
 *
 * $image = new EasyGD();
 * $image->startFromFile('image.jpg')
 *       ->crop(100, 50)
 *       ->width(50)
 *       ->save('/path', 'newfile.'.$image->extension)
 *       ->display()
 *       ->end();
 *
 * @author Éber Freitas Dias <eusou@eberfdias.com>
 * @license http://www.gnu.org/copyleft/lgpl.html GNU Lesser General Public License 3
 * 
 * @package EasyGD
 * @version 1.0 - April 3rd, 2009 - 11:00am
 **/

class EasyGD {
    /******************************/
    /* GLOBALS FOR ERROR HANDLING */
    /******************************/
    
    const ERROR_OPEN = "It wasn't possible to open the provided image.";
    const ERROR_SUPPORT = "This file format is not suported by EasyGD. Use a jpg or png image instead.";
    const ERROR_RESOURCE = "There is no resource for this function. Please, run EasyGD::startFrom* to define one.";
    const ERROR_CROP = "One of the dimensions provided are bigger than the actual image.";
    const ERROR_ARGUMENTS = "The function was called with missing arguments.";
    const ERROR_SERVER = "The server doesn't support this function. Please, contact you server admin.";
    const ERROR_QUALITY = "The quality value is out of boundaries.";
    
    /**********************/
    /* GENERAL ATTRIBUTES */
    /**********************/
    
    /**
     * @var array List of supported extensions
     **/
    private $ext2mime = array(
        'jpg' => 'image/jpeg',
        'png' => 'image/png'
    );
    
    /**
     * @var array List of supported mime types
     **/
    private $mime2ext = array(
        'image/jpeg'  => 'jpg',
        'image/pjpeg' => 'jpg',
        'image/png'   => 'png'
    );
    
    /**
     * @var array List of positions for cropping. The first letter represents the x axis (Left, Center, Right) and the second letter represents the y axis (Top, Center, Bottom)
     **/
    private $cropTypes = array(
        'LT', 'CT', 'RT',
        'LC', 'CC', 'RC',
        'LB', 'CB', 'RB'
    );
    
    /**
     * @var resource Variable where the current resource will be stored
     **/
    private $resource;
	
    /**
     * @var array This variable holds a list of functions to be used by specific file types
     **/
    private $func;
    
    /**
     * @var int This stores the image quality (for JPG, from 0 to 100) and compression (for PNG, from 0 to 9)
     **/
    private $quality;
    
    /**
     * @var int Width of the image
     **/
    public $x;
    
    /**
     * @var int Height of the image
     **/
    public $y;
    
    /**
     * @var string Holds the original file name
     **/
    public $filename;
    
    /**
     * @var string Holds the filename assigned by EasyGD::save
     **/
    public $newFilename;
    
    /**
     * @var string Extension from the current image being manipulated
     **/
    public $extension;
    
    /**
     * You can pass a file name / file array, a url or a resource. In each case,
     * check the refering function to get the right arguments:
     * 
     * file name or file array  => EasyGD::startFromFile
     * url                      => EasyGD::startFromUrl
     * resource                 => EasyGD::startFromResource
     *
     * You can start the class without any variables and create a new image latter with
     * the functions from above.
     *
     * @return mixed
     **/
    public function __construct() {
        $argsnum = func_num_args();
        $args = func_get_args();
        
        if($argsnum > 0) {
            if(gettype($args[0]) == 'resource') {
                return $this->startFromResource($args[0], @$args[1], @$args[2]);
            }
            
            if(strpos($args[0], 'http://')!== false) {
                return $this->startFromUrl($args[0]);
            }
            
            if(is_string($args[0]) || is_array($args[0])) {
                return $this->startFromFile($args[0]);
            }
        }
    }
    
    /**
     * Terminates the resource and free up some memory
     **/
    public function __destruct() {
        $this->reset();
    }
    
    /*******************/
    /* START FUNCTIONS */
    /*******************/
    
    /**
     * Use this function to start EasyGD from a file.
     * @param string|array $file This can receive a string with the filename Ex. 'image.jpg' or an array from the $_FILES global Ex. $_FILES['myfile']
     **/
    public function startFromFile($file) {
        if(!is_array($file)) {
            $ext = strtolower(substr(strrchr($file, "."), 1));
            
            if(!file_exists($file)) {
                $this->error(self::ERROR_OPEN);
                return false;
            }
            
            if(!$this->checkSupport(null, $ext)) {
                $this->error(self::ERROR_SUPPORT);
                return false;
            }
            
            $this->filename  = $file;
            $this->extension = $ext;
        } else {
            if($file['error'] != 0) {
                $this->error(self::ERROR_OPEN);
            }
            
            if(!$this->checkSupport($file['type'])) {
                $this->error(self::ERROR_SUPPORT);
                return false;
            }
            
            $this->filename  = $file['name'];
            $this->extension = $this->mime2ext[$file['type']];
            $file            = $file['tmp_name'];
        }
        
        $this->func              = $this->getFunction($this->extension);
        $this->resource          = call_user_func($this->func['create'], $file);
        list($this->x, $this->y) = getimagesize($file);
        
        return $this;
    }
    
    /**
     * With this function you can start EasyGD from an already started resource
     * @param resource $resource The image resource to be used
     * @param string $ext The original image extension
     * @param string $filename The original filename, not exactly necessary
     **/
    public function startFromResource($resource, $ext = null, $filename = null) {
        if(is_null($ext)) {
            $this->error(self::ERROR_ARGUMENTS);
            return false;
        }
        
        if(!$this->checkSupport(null, $ext)) {
            $this->error(self::ERROR_SUPPORT);
            return false;
        }
        
        $this->resource  = $resource;
        $this->filename  = !is_null($filename) ? $filename : '';
        $this->extension = $ext;
        $this->x         = imagesx($resource);
        $this->y         = imagesy($resource);
        $this->func      = $this->getFunction($this->extension);
        
        return $this;
    }
    
    /**
     * With this function you can start EasyGD to use a remote image from a URL.
     * @param string $url The URL of the remote image
     **/
    public function startFromUrl($url) {
        $ext = strtolower(substr(strrchr($url, "."), 1));
        
        if(!$this->checkSupport(null, $ext)) {
            $this->error(self::ERROR_SUPPORT);
            return false;
        }
        
        if(function_exists('file_get_contents')) {
            $img = file_get_contents($url);
        } else {
            if(function_exists('curl_init')) {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $url);
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
                $img = curl_exec($ch);
                curl_close($ch);
            } else {
                $this->error(self::ERROR_SERVER);
                return false;
            }
        }
        
        $this->resource  = imagecreatefromstring($img) or $this->error(self::ERROR_OPEN);
        $this->filename  = strtolower(substr(strrchr($url, "/"), 1));
        $this->extension = $ext;
        $this->x         = imagesx($this->resource);
        $this->y         = imagesy($this->resource);
        $this->func      = $this->getFunction($this->extension);
        
        return $this;
    }
    
    /******************/
    /* OUTPUT METHODS */
    /******************/
    
    /**
     * This function displays the image on the browser directly
     **/
    public function display() {
        if(!$this->checkResource()) {
            $this->error(self::ERROR_RESOURCE);
            return false;
        }
        
        header('Content-Type: '.$this->ext2mime[$this->extension]);
        if(call_user_func_array($this->func['display'], array($this->resource, null, $this->quality))) {
            return $this;
        }
        
        return false;
    }
    
    /**
     * This function saves the resource to an image file. You can set the path where the file
     * will be saved as well as the file name. If none is given, the image will be saved on
     * the current path and the file name will be the timestamp of the moment which is pretty handy.
     * @param string $path Param to hold the path where the image will be stored. It should finish with a "/".
     * @param string $filename The image name to be used. Ex. 'image.jpg'. Don't forget to insert the right extension. You can use EasyGD::extension to do that.
     **/
    public function save($path = '', $filename = null) {
        if(!empty($path) && $path{strlen($path)-1} != "/") $path = $path . "/";
        
        if(is_null($filename)) $filename = time().".".$this->extension;
        
        $this->newFilename = $filename;
        
        if(call_user_func_array($this->func['display'], array($this->resource, $path.$filename, $this->quality))) {
            return $this;
        }
        
        return false;
    }
    
    /*****************/
    /* SETUP METHODS */
    /*****************/
    
    /**
     * This function sets the quality of the image. If you are using JPEG images, you have to enter a number
     * from 0 to 100 where the bigger the number, better the quality. With PNG images this number means compression
     * and you have to enter a number from 0 to 9 where the bigger the number, more compression it has, which means
     * less quality and a smaller file size.
     * @param int $quality The number to set the image quality.
     **/
    public function setQuality($quality) {
        if(!$this->checkResource()) {
            $this->error(self::ERROR_RESOURCE);
            return false;
        }
        
        switch($this->extension) {
            case 'jpg':
                if($quality > 100 || $quality < 0) {
                    $this->error(self::ERROR_QUALITY);
                    return false;
                }
                break;
            case 'png':
                if($quality > 9 || $quality < 0) {
                    $this->error(self::ERROR_QUALITY);
                    return false;
                }
                break;
        }
        
        $this->quality = $quality;
        
        return $this;
    }
    
    /**
     * If you are using transparent PNG images you might want to activate its
     * alpha channel. That is what this function does.
     **/
    public function setAlpha() {
        if($this->extension == 'png') {
            imagealphablending($this->resource, true);
            imagesavealpha($this->resource, true);
        }
        
        return $this;
    }
    
    /************************/
    /* MANIPULATION METHODS */
    /************************/
    
    /**
     * Function to resize an image
     * @param int $width Width to be resized
     * @param int $height Height to be resized
     **/
    public function resize($width, $height) {
        if(!$this->checkResource()) {
            $this->error(self::ERROR_RESOURCE);
            return false;
        }
        
	$newResource = imagecreatetruecolor($width, $height);
	imagecopyresampled($newResource, $this->resource, 0, 0, 0, 0, $width, $height, $this->x, $this->y);
	
	$this->startFromResource($newResource, $this->extension, $this->filename);
	
	return $this;
    }
    
    
    /**
     * Shortcut for the EasyGD::resize function where you can set a with and the height is calculated proportionally
     * @param int $width New image width to be resized
     **/
    public function width($width) {
	return $this->resize($width, round(($this->y / $this->x) * $width));
    }
    
    /**
     * Shortcut for the EasyGD::resize function where you can set a height and the width is calculated proportionally
     * @param int $height New image height to be resized
     **/
    public function height($height) {
	return $this->resize(round($this->x / $this->y) * $height, $height);
    }
    
    
    /**
     * Function where you can crop an image. You can use the pre-defined patterns with $type
     * or define your own position with $x and $y
     * @param int $width Width of the crop
     * @param int $height Height of the crop
     * @param string $type One of the EasyGD::cropTypes
     * @param int $x The X axis to start the crop
     * @param int $y The Y axis to start the crop
     */
    public function crop($width, $height, $type = 'CC', $x = null, $y = null) {
	if(!$this->checkResource()) {
	    $this->error(self::ERROR_RESOURCE);
	    return false;
	}
	
	if($width > $this->x || $height > $this->y) {
	    $this->error(self::ERROR_CROP);
	}
	
	if(!in_array($type, $this->cropTypes) || is_null($type)) {
	    $type = 'CC';
	}
	
	$leftX = $this->x - $width;
	$leftY = $this->y - $height;
	
	if(is_null($x) || is_null($y)) {
	    switch($type) {
		case "LT":
		    $x = $y = 0;
		    break;
		case "CT":
		    $x = round($leftX / 2); $y = 0;
		    break;
                case "RT":
                    $x = $leftX; $y = 0;
                    break;
                case "LC":
                    $x = 0; $y = round($leftY / 2);
                    break;
                case "CC":
                    $x = round($leftX / 2); $y = round($leftY / 2);
                    break;
                case "RC":
                    $x = $leftX; $y = round($leftY / 2);
                    break;
                case "LB":
                    $x = 0; $y = $leftY;
                    break;
                case "CB":
                    $x = round($leftX / 2); $y = $leftY;
                    break;
                case "RB":
                    $x = $leftX; $y = $leftY;
                    break;
	    }
	} else {
            if($x + $width > $this->x)
                $x = $leftX;
            if($y + $height > $this->y)
                $y = $leftY;
	}
	
	$newResource = imagecreatetruecolor($width, $height);
	imagecopy($newResource, $this->resource, 0, 0, $x, $y, $this->x, $this->y);
	
	$this->startFromResource($newResource, $this->extension, $this->filename);
	
	return $this;
    }
    
    /**
     * This will destroy the current resource and reset the variables so you can start
     * it all over again! :)
     **/
    public function end() {
        return $this->reset();
    }
    
    /********************/
    /* INTERNAL METHODS */
    /********************/
    
    private function reset() {
        if(!$this->checkResource()) {
            return true;
        }
	
        imagedestroy($this->resource);
        
        $this->extension = null;
        $this->x         = null;
        $this->y         = null;
        $this->func      = null;
        $this->quality   = null;
        
        return true;
    }
    
    private function checkResource() {
	return gettype($this->resource) == 'resource';
    }
    
    private function checkSupport($mime = null, $ext = null) {
        if(!is_null($mime)) {
            $mimes = array_keys($this->mime2ext);
            return in_array($mime, $mimes);
        } elseif(!is_null($ext)) {
            return in_array($ext, $this->mime2ext);
        }
        
        return false;
    }
    
    private function getFunction($ext) {
	switch($ext) {
	    case "jpg":
		$this->quality = 100;
		return array(
		    'create'=> 'imagecreatefromjpeg',
		    'display'=> 'imagejpeg'
		);
		break;
	    case "png":
		$this->quality = 0;
                return array(
                    'create' => 'imagecreatefrompng',
                    'display' => 'imagepng'
                );
                break;
	    default:
		$this->error(self::ERROR_SUPPORT);
		return false;
	}
    }
    
    private function error($msg) {
        $this->reset();
	trigger_error($msg, E_USER_ERROR);
    }
}
?>